﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
/*
 * Communiactor used to reach PicaNC
 * 
 */ 
namespace VirtualOperator
{
    public class Communicator
    {

        public string NetworkName { get; private set; }
        private VOConfig config;
        private List<string> messages = new List<string>();
        public List<string> Messages { get { return messages; } }

        private Socket masterSocket;
        private Socket slaveSocket;
        private bool slaveConnected;
        private bool masterConnected;


        public bool Connected { get { return masterConnected && slaveConnected; } }
        byte[] slaveBuffor = new byte[4096];
        byte[] masterBuffor = new byte[4096];
        IAsyncResult asyn;

        public string Message { get; private set; }

        public Communicator(VOConfig config)
        {
            this.config = config;
            this.Message = "Brak połączenia z PicsNC...";
        }


        public bool Init()
        {
            try
            {
                this.Message = "Łączenie z PicsNC...";
                masterSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                slaveSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                IPEndPoint server = new IPEndPoint(IPAddress.Loopback, config.Port);
                IPEndPoint client = new IPEndPoint(IPAddress.Loopback, config.Port);
                masterSocket.Connect(server);

                this.Message = "Autoryzacja...";
               // asyn = socket.BeginReceive(buffor, 0, buffor.Length, SocketFlags.None, OnDataReceived, this);
                string recieve = this.Query(masterSocket, String.Format("Register {0}", config.Name));
                string[] param = recieve.Split(' ');
                if (param.Length == 3 && param[0] == "Register" && param[1] == "OK")
                {
                    NetworkName = param[2];
                    masterConnected = true;
                    slaveSocket.Connect(client);
                    slaveConnected = true;
                    this.Query(slaveSocket, String.Format("Register {0}", config.Name));
                    slaveSocket.BeginReceive(slaveBuffor, 0, slaveBuffor.Length, SocketFlags.None, OnDataReceived, slaveSocket);
                }
                this.Message = "Połączono z PicsNC.";
                
            }
            catch (SocketException)
            {
                this.Message = "Nie udalo się połączyć z PicsNC.";
                masterConnected = false;
            }
            int waitingAll = 0;
            int section = 50;
            while (!slaveConnected && waitingAll < 5000) {
                Thread.Sleep(section);
                waitingAll += section;
            }

             return Connected; 
        }


        private void OnDataReceived(IAsyncResult asyn)
        {
            
            int r = 0;
            try { r = slaveSocket.EndReceive(asyn); }
            catch (SocketException)
            {
                slaveSocket.Close();
                slaveConnected = false;
                return;
            }
            if (r == 0)
            {
                slaveSocket.Close();
                slaveConnected = false;
                return;
            }
            string recv = Encoding.ASCII.GetString(slaveBuffor, 0, r);
            ProcessQuery(recv);
            slaveSocket.BeginReceive(slaveBuffor, 0, slaveBuffor.Length, SocketFlags.None, OnDataReceived, slaveSocket);
        }

        private void ProcessQuery(string recv)
        {
   
            string[] query = recv.Split(' ');


          //protocol implementation TODO
        }

        private string Query(Socket socket, string query)
        {
            byte[] buffor = new byte[4096];
            string response = "";
            try
            {
                socket.Send(Encoding.ASCII.GetBytes(query));
                int r = socket.Receive(buffor);
                response = Encoding.ASCII.GetString(buffor, 0, r);
                // socket.BeginReceive(buffor, 0, buffor.Length, SocketFlags.None, OnDataReceived, this);
            }
            catch (SocketException)
            {
                
            }
            catch (NullReferenceException)
            {
               
            }
            return response;
        
        }


        private string Query(string query)
        {
            string response = "";
            try
            {
                masterSocket.Send(Encoding.ASCII.GetBytes(query));
                int r = masterSocket.Receive(masterBuffor);
                response = Encoding.ASCII.GetString(masterBuffor, 0, r);
               // socket.BeginReceive(buffor, 0, buffor.Length, SocketFlags.None, OnDataReceived, this);
            }
            catch (SocketException)
            {
                masterConnected = false;
            }
            catch (NullReferenceException)
            {
                masterConnected = false; 
            }
            return response;
        }

        public Dictionary<int, VirtualNetwork.Matrix> GetMatrixes()
        {
            Dictionary<int, VirtualNetwork.Matrix> nodes = new Dictionary<int, VirtualNetwork.Matrix>();
            VirtualNetwork.Matrix node;
            string command = Query("GetAccessibleMatrixes");
            string [] param = command.Split(' ');
            if (param[0] == "GetAccessibleMatrixes" && param[1] == "OK" && param.Length == 3)
            {
                char[] spliter = { '#' };
                param = param[2].Split(spliter, StringSplitOptions.RemoveEmptyEntries);
                foreach (string s in param)
                { 
                     node = StringToNode(s)  ;
                     nodes.Add(node.ManagerID, node);
                }
            }

            return nodes;
        }

        private VirtualNetwork.Matrix StringToNode(string comand)
        {
          VirtualNetwork.Matrix node;
          TransportLayer.NodeToolsLibrary.Point point;
          int id;
          string [] param = comand.Split(':');
         
              id = Int32.Parse(param[0]);
              point = new TransportLayer.NodeToolsLibrary.Point(Int32.Parse(param[3]), Int32.Parse(param[4]));
              node = new VirtualNetwork.Matrix(id, param[1], param[2], point);
              
              return node;
        }

        public int CreateNewVirtualNetwork(int delay, int usage, float errorRate)
        {
            int vnn = -1;
            string query = String.Format("AddVirtualNetwork {0}:{1}:{2}", delay, errorRate, usage);
            string response = Query(query);
            string [] param = response.Split(' ');
            if (param.Length == 3 && param[0] == "AddVirtualNetwork" && param[1] == "OK")
                vnn = Int32.Parse(param[2]);
            return vnn;
        }

        public bool SetVirtualLink(int vnID, int src, int trg, int maxDelay, float maxLinkErrorRate, int capacity, out int srcOut, out int trgOut, out int linkId)
        {
            string response = Query(String.Format("AddVirtualLink {0} {1}:{2}:{3}:{4}:{5}", vnID, src, trg, maxDelay, maxLinkErrorRate, capacity));
            string[] param = response.Split(' ');
            if (param.Length == 5 && param[1] == "OK" && param[0] == "AddVirtualLink")
            {
                srcOut = Int32.Parse(param[2]);
                trgOut = Int32.Parse(param[3]);
                linkId = Int32.Parse(param[3]);
                return true;
            }
            srcOut = 0;
            trgOut = 0;
            linkId = 0;
            return false;
        }

        public NwTTP AddSink(int src, int topologyID)
        {
            string response = Query(String.Format("AddSinkTTP {0} {1}", topologyID, src));
            string[] param = response.Split(' ');
            if (param.Length == 4 && param[1] == "OK" && param[0] == "AddSinkTTP")
            {
                int managerId = Int32.Parse(param[2]);
                int id = Int32.Parse(param[2]);
                int port = Int32.Parse(param[3]);
                NwTTP nwttp = new NwTTP(id,managerId, "Sink_" + param[2], Direction.SINK,port);
                return nwttp;
            }
            else return null;
        }

        public NwTTP AddSource(int trg, int topologyID)
        {
            string response = Query(String.Format("AddSourceTTP {0} {1}", topologyID, trg));
            string[] param = response.Split(' ');
            if (param.Length == 4 && param[1] == "OK" && param[0] == "AddSourceTTP")
            {
                int managerId = Int32.Parse(param[2]);  //todo
                int id = Int32.Parse(param[2]);
                int port = Int32.Parse(param[3]);
                NwTTP nwttp = new NwTTP(id, managerId, "Source_" + param[2], Direction.SOURCE, port);
                return nwttp;
            }
            else return null;
        }

        public int Connect(UserConnection nc, int topologyID)
        {
            string path="";
            foreach (VirtualNetwork.Link link in nc.links)
            {
                path += link.Source.ID + ":" + link.Id + ":";
            }
            path += nc.TargetMatrix.ID;
            string response = Query(String.Format("NewNetworkConnection {0} {1} {2} {3} {4}", topologyID, nc.SourceNwTTP.ID, nc.TargetNwTTP.ID, path, nc.Capacity));
            string[] param = response.Split(' ');
            if (param.Length == 4 && param[0] == "NewNetworkConnection" && param[1] == "OK")
            {
                return Int32.Parse(param[2]);
            }
            else return -1;
        }

        public bool Teardown(UserConnection nc, int topologyID)
        {
            string response = Query(String.Format("TeardownNetworkConnection {0} {1}", topologyID, nc.ID));
            string[] param = response.Split(' ');
            if (param.Length == 2 && param[0] == "TeardownNetworkConnection" && param[1] == "OK")
            {
                return true;
            }
            else return false;

        }

        public bool RemoveSinkSource(int topolgyID, int id)
        {
            string response = Query(String.Format("remove_sink_source {0} {1}", topolgyID, id));
            string[] param = response.Split(' ');
            if (param.Length == 2 && param[0] == "remove_sink_source" && param[1] == "OK")
            {
                return true;
            }
            else return false;
        
        }

        public bool RemoveVirtualLink(int sourceId, int targetId)
        {
            string response = Query(String.Format("RemoveVirtualLink {0} {1}", sourceId, targetId));
            string[] param = response.Split(' ');
            if (param.Length == 2 && param[0] == "RemoveVirtualLink" && param[1] == "OK")
            {
                return true;
            }
            else return false;
        
        }


        public bool RemoveVirtualNetwork(int id)
        {
            string response = Query(String.Format("RemoveVirtualNetwork {0}", id));
            string[] param = response.Split(' ');
            if (param.Length == 2 && param[0] == "RemoveVirtualNetwork" && param[1] == "OK")
            {
                return true;
            }
            else return false;

        }

        public bool ByeVNO()
        {
            string response = Query(String.Format("Logout {0}", config.Id));
            string[] param = response.Split(' ');
            if (param.Length == 2 && param[0] == "Logout" && param[1] == "OK")
            {
                return true;
            }
            else return false;

        }

        public string ConsoleQuery(string query)
        {
            return this.Query(query);
        }
        public void Clean()
        {
            if(masterSocket != null)
                 masterSocket.Close();
            if (slaveSocket != null)
                slaveSocket.Close();
            masterConnected = false;
            slaveConnected = false;
        }
    }
}
